package com.dingwen.treasure.mybatisplus.controller;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.dingwen.treasure.base.controller.BaseCrudController;
import com.dingwen.treasure.base.controller.BaseViewController;
import com.dingwen.treasure.base.convert.model.impl.AbstractBeanConvertImpl;
import com.dingwen.treasure.base.util.ReflectUtil;
import com.dingwen.treasure.base.valid.ValidGroup;
import com.dingwen.treasure.base.pojo.vo.Result;
import com.dingwen.treasure.base.pojo.vo.ResultGenerator;
import com.dingwen.treasure.beansearch.controller.AbstractBaseControllerBeanSearchImpl;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 基础 crud 封装 controller mybatisplus 实现 </br>
 * <p>泛型说明：</p>
 * <ul>
 *     <li>P： po 持久化对象</li>
 *     <li>D: dto 数据传输对象</li>
 *     <li>V： vo 视图对象</li>
 * </ul>
 *
 * <p> 优化记录： </p>
 * <ul>
 *     <li> getDto（）优化：多线程条件下加同步锁较少调用次数,只是获取值，修改为 volatile 一旦赋值多线程立刻可见 </li>
 * </ul>
 *
 * @author dingwen
 * @date 2021.06.29 15:09
 * @since 1.0.0-SNAPSHOT
 */
@Slf4j
public abstract class AbstractBaseControllerMybatisPlusImpl
        <
                P extends Serializable, // 实体
                D extends AbstractBeanConvertImpl<P, D, V>, // 数据传输对象
                V // 视图对象
                >
        extends AbstractBaseControllerBeanSearchImpl<P> implements BaseCrudController<D>, BaseViewController {
    /**
     * 对象主键 id 字段名称
     */
    public static final String FIELD_NAME_BY_ID = "id";

    /**
     * get 方法前缀
     */
    public static final String METHOD_PREFIX_NAME = "get";


    /**
     * 修改异常： 修改时必须指定ID
     */
    public static final String UPDATE_EXCEPTION_TIPS = "进行修改、删除操作时对象 id 不能为空";

    /**
     * dto
     */
    protected volatile D dto;

    /**
     * base service
     */
    @Autowired
    protected IService<P> iService;


    /* --------------------------------------------查询--------------------------------------------*/


    /**
     * 根据Id查询，返回单个实体
     *
     * @param id 数据表主键
     * @return {@link Result}
     */
    @Override
    @ApiOperation("根据主键查询,返回单个对象，默认开启持久层对象到视图对象的转换")
    @ApiOperationSupport(author = "dingwen")
    @ApiImplicitParam(value = "主键", name = "id")
    @GetMapping("/{id}")
    public Result<?> findById(@PathVariable(value = "id") Serializable id) {
        // 数据库对象
        P po = iService.getById(id);
        // 数据传输对象
        D dto = getDTO();
        if (enableBoToVo()) {
            // 需要进行转换
            // 视图对象
            V vo = dto.poToVo(po);
            return ResultGenerator.genSuccessResult(vo);
        }
        return ResultGenerator.genSuccessResult(po);
    }


    /**
     * 根据List<id>查找实体
     *
     * @param ids id 传参示例： 1,2,3 集合长度至少为2
     * @return {@link Result}
     */
    @Override
    @ApiOperation("根据主键集合查询，返回对象列表，默认开启持久层对象到视图对象的转换")
    @ApiOperationSupport(author = "dingwen")
    @ApiImplicitParam(value = "主键List集合", name = "ids", dataType = "body")
    @PostMapping("/ids")
    public Result<List<?>> findByIds(@RequestBody List<Serializable> ids) {

        //数据库对象
        List<P> pos = iService.listByIds(ids);
        if (enableBoToVo()) {
            // 页面对象
            List<V> vos = pos.parallelStream()
                    .map(po -> getDTO().poToVo(po))
                    .collect(Collectors.toList());
            return ResultGenerator.genSuccessResult(vos);
        }
        return ResultGenerator.genSuccessResult(pos);

    }

    /**
     * 无条件查询所有
     *
     * @return {@link Result}
     */
    @ApiOperation("查询所有，返回对象列表，默认开启持久层对象到视图对象的转换（数据量大时慎用）")
    @ApiOperationSupport(author = "dingwen")
    @GetMapping
    @Override
    public Result<?> findAll() {
        // 数据库对象
        List<P> pos = iService.list();
        if (enableBoToVo()) {
            // 页面对象
            List<V> vos = pos.parallelStream()
                    .map(po -> getDTO().poToVo(po))
                    .collect(Collectors.toList());
            return ResultGenerator.genSuccessResult(vos);
        }
        return ResultGenerator.genSuccessResult(pos);
    }


    /**
     * 无条件分页查询
     *
     * @param pageSize 页面大小
     * @param pageNum  页面num
     * @return {@link Result}
     */
    @Override
    @ApiOperation("分页查询,返回 对象 page")
    @ApiOperationSupport(author = "dingwen")
    @ApiImplicitParams(
            {
                    @ApiImplicitParam(name = "pageSize", value = "页面大小"),
                    @ApiImplicitParam(name = "pageNum", value = "页码数")
            }
    )
    @GetMapping("/page")
    public Result<?> page(@RequestParam(name = "pageSize", required = false, defaultValue = "10") Integer pageSize,
                          @RequestParam(name = "pageNum", required = false, defaultValue = "1") Integer pageNum) {

        IPage<P> iPage = new Page<>(pageNum, pageSize);

        IPage<P> page = iService.page(iPage);

        if (enableBoToVo()) {
            List<V> vos = page.getRecords()
                    .parallelStream()
                    .map(po -> getDTO().poToVo(po)).collect(Collectors.toList());
            page.setRecords((List<P>) vos);
        }
        return ResultGenerator.genSuccessResult(page);
    }


    /**
     * 获取数据表总记录条数
     *
     * @return {@link Result}
     */
    @Override
    @ApiOperation("获取数据表总记录条数，返回统计数量")
    @ApiOperationSupport(author = "dingwen")
    @GetMapping("/count")
    public Result<Integer> count() {
        return ResultGenerator.genSuccessResult(iService.count());
    }

    /**
     * 通过 id 查询是否存在 </br>
     * <ol>
     *     <li>true: 存在</li>
     *     <li>false: 不存在</li>
     * </ol>
     *
     * @param id 主键id
     * @return {@link Result}
     */
    @Override
    @ApiOperation("根据对象 id 查询对象是否存在,返回布尔值")
    @ApiOperationSupport(author = "dingwen")
    @GetMapping("/exists/{id}")
    public Result<Boolean> existsById(@PathVariable("id") Serializable id) {
        return ResultGenerator.genSuccessResult(!ObjectUtils.isEmpty(iService.getById(id)));
    }



    /* --------------------------------------------修改--------------------------------------------*/

    /**
     * 修改,对象 id ,主键必须有值,默认获取 t 中的 id 属性
     *
     * @param dto 数据传输对象
     * @return {@link Result}
     */
    @Override
    public Result<Boolean> modify(D dto) {
        return ResultGenerator.genResult(iService.updateById(dto.dtoToPo(dto)));
    }

    /**
     * 修改,对象 id ,主键必须有值
     *
     * @param dto            数据传输对象
     * @param fieldNameForId 指定Id 字段名称
     * @return {@link Result}
     */
    @Override
    @ApiOperation("修改数据表单条记录,返回布尔值")
    @ApiOperationSupport(author = "dingwen")
    @PutMapping("/{id}")
    public Result<Boolean> modify(@RequestBody @Validated({ValidGroup.Modify.class}) D dto,
                                  @PathVariable("id") Serializable id,
                                  @RequestParam(value = "fieldNameForId", required = false) String fieldNameForId) {
        return modify(dto);
    }

    /**
     * 修改,批量修改,对象主键必须要有值,,默认获取 t 中的 id 属性
     *
     * @param dtos           数据传输对象列表
     * @param fieldNameForId 指定Id 字段名称
     * @return {@link Result}
     */
    @Override
    @ApiOperation("批量修改,返回布尔值")
    @ApiOperationSupport(author = "dingwen")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "ts", value = "实体Json对象集合"),
            @ApiImplicitParam(value = "指定对象 id 属性名称", name = "id")
    })
    @PutMapping
    public Result<Boolean> batchModify(@RequestBody List<D> dtos, @RequestParam(value = "fieldNameForId", required =
            false) String fieldNameForId) {
        // 检查
        if (StrUtil.isBlank(fieldNameForId)) {
            fieldNameForId = FIELD_NAME_BY_ID;
        }
        checkId(dtos, fieldNameForId);
        return batchModify(dtos);
    }

    /**
     * 修改,批量修改,对象主键必须要有值
     *
     * @param dtos 数据传输对象列表
     * @return {@link Result}
     */
    @Override
    public Result<Boolean> batchModify(@RequestBody List<D> dtos) {
        List<P> pos = dtos.parallelStream().map(dto -> dto.dtoToPo(dto)).collect(Collectors.toList());
        return ResultGenerator.genResult(iService.updateBatchById(pos));
    }

    /**
     * 检查 t 实体 id是否存在
     *
     * @param dtos 数据传输对象列表
     */
    public void checkId(List<D> dtos, String fieldNameForId) {
        dtos.parallelStream().forEach(dto -> checkId(dto, fieldNameForId));
    }


    /**
     * 检查 dto 实体
     *
     * @param dto            数据传输对象
     * @param fieldNameForId id字段名
     */
    public void checkId(D dto, String fieldNameForId) {
        try {
            Class<? extends Serializable> tClass = (Class<? extends Serializable>) dto.getClass();
            Field fieldForId = tClass.getDeclaredField(fieldNameForId);
            fieldForId.setAccessible(true);

            String fieldName = fieldNameForId.substring(0, 1);
            String fieldNameUpper = fieldNameForId.replaceFirst(fieldName, fieldName.toUpperCase());
            String methodName = METHOD_PREFIX_NAME.concat(fieldNameUpper);

            Method method = tClass.getDeclaredMethod(methodName);

            Assert.notNull(method.invoke(dto), UPDATE_EXCEPTION_TIPS);
        } catch (Exception e) {
            log.error("BaseController 类型检查异常,message: {}", e.getMessage());
            throw new IllegalArgumentException(UPDATE_EXCEPTION_TIPS);
        }

    }

    /* --------------------------------------------新增--------------------------------------------*/

    /**
     * 批量保存
     *
     * @param dtos 数据传输对象列表
     * @return {@link Result}
     */
    @Override
    @ApiOperation("批量添加，返回布尔值")
    @ApiOperationSupport(author = "dingwen")
    @ApiImplicitParam(name = "ts", value = "条件集合Json对象")
    @PostMapping("/batch")
    public Result<Boolean> batchSave(@RequestBody List<D> dtos) {
        List<P> pos = dtos.parallelStream().map(dto -> dto.dtoToPo(dto)).collect(Collectors.toList());
        return ResultGenerator.genResult(iService.saveBatch(pos));
    }

    /**
     * 保存
     *
     * @param dto 数据传输对象
     * @return {@link Result}
     */
    @Override
    @ApiOperation("保存一个对象到数据库，返回布尔值")
    @ApiOperationSupport(author = "dingwen")
    @ApiImplicitParam(value = "数据传输对象", name = "dto", dataType = "body")
    @PostMapping
    public Result save(@RequestBody @Validated({ValidGroup.Save.class}) D dto) {
        P p = dto.dtoToPo(dto);
        boolean result = iService.save(p);
        return result ? ResultGenerator.genSuccessResult(dto.poToVo(p)) : ResultGenerator.genFailureResult();
    }


    /* --------------------------------------------删除--------------------------------------------*/


    /**
     * 删除
     *
     * @param id id
     * @return {@link Result}
     */
    @Override
    @ApiOperation("删除单条记录，返回布尔值")
    @ApiOperationSupport(author = "dingwen")
    @ApiImplicitParam(name = "id", value = "主键")
    @DeleteMapping("/{id}")
    public Result<Boolean> removeById(@PathVariable("id") Serializable id) {
        return ResultGenerator.genResult(iService.removeById(id));
    }

    /**
     * 根据主键集合批量删除
     *
     * @param ids id
     * @return {@link Result}
     */
    @Override
    @ApiOperation("批量删除记录,返回布尔值")
    @ApiOperationSupport(author = "dingwen")
    @ApiImplicitParam(name = "ids", value = "主键集合")
    @DeleteMapping("/batch")
    public Result<Boolean> batchRemoveByIds(@RequestBody List<Serializable> ids) {
        return ResultGenerator.genResult(iService.removeByIds(ids));
    }


    /**
     * 获取dto
     *
     * @return {@link D} dto 数据传输对象
     */
    @SneakyThrows(Throwable.class)
    public D getDTO() {
        if (!Objects.isNull(dto)) {
            return dto;
        }
        Class<D> dtoClass = ReflectUtil.getClassForGeneric(1, this);
        dto = dtoClass.newInstance();
        return dto;
    }
}
